/*
 * #%L
 * Osm2garminAPI
 * %%
 * Copyright (C) 2011 Frantisek Mantlik <frantisek at mantlik.cz>
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */
package be.dch.srtm2osm;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.log4j.Logger;

import be.dch.Config;

/**
 * 
 * @author fm
 */
public class Srtm {

    private static Logger LOG = Logger.getLogger(Srtm.class);

    public int[][] data = new int[1201][1201];
    private String path = Config.getInstance().getSrtm2OsmConfig().getCacheDir();
    private String url = Config.getInstance().getSrtm2OsmConfig().getSrtmURL();
    static final String[] REGIONS = { "Eurasia", "Africa", "Australia", "Islands", "North_America", "South_America" };
    int lastIndex = 1;
    static String[] lists = new String[Srtm.REGIONS.length];
    private static final Map<String, Integer> regionMap = new HashMap<String, Integer>();

    public static String getName(final int lon, final int lat) {
	String dirlat = "N";
	if (lat < 0) {
	    dirlat = "S";
	}
	String dirlon = "E";
	if (lon < 0) {
	    dirlon = "W";
	}
	String st = String.valueOf(Math.abs(lat));
	while (st.length() < 2) {
	    st = "0" + st;
	}
	String fname = dirlat + st;
	st = String.valueOf(Math.abs(lon));
	while (st.length() < 3) {
	    st = "0" + st;
	}
	fname = fname + dirlon + st + ".hgt";
	return fname;
    }

    public boolean load(final int lon, final int lat) {
	// loads SRTM data for the lon,lat
	final String fname = Srtm.getName(lon, lat);
	final String region = Srtm.findRegion(fname, this.path, this.url);
	if (region == null) {
	    return false;
	}
	File srtmFile;
	File srtmZipFile;
	if (!this.path.equals("")) {
	    srtmFile = new File(this.path + "/" + region + "/" + fname);
	    srtmZipFile = new File(this.path + "/" + region + "/" + fname + ".zip");
	} else {
	    srtmFile = new File(fname);
	    srtmZipFile = new File(fname + ".zip");
	}
	InputStream s;
	if (srtmZipFile.exists()) {
	    try {
		// try zip file
		final ZipFile zf = new ZipFile(srtmZipFile);
		final ZipEntry entry = zf.getEntry(fname);
		s = zf.getInputStream(entry);
		s.close();
	    } catch (final IOException ex) {
		// broken download, try again
		srtmZipFile.delete();
	    }
	}
	if (!(srtmFile.exists() || srtmZipFile.exists() || this.download(fname))) {
	    // SRTMS.SRTMS.put(100 * lon + lat, null);
	    return false;
	}
	if (srtmFile.exists()) {
	    try {
		s = new FileInputStream(srtmFile);
	    } catch (final FileNotFoundException ex) {
		Srtm.LOG.error(ex);
		return false;
	    }
	} else { // try zip file
	    try {
		// try zip file
		final ZipFile zf = new ZipFile(srtmZipFile);
		final ZipEntry entry = zf.getEntry(fname);
		s = zf.getInputStream(entry);

	    } catch (final Exception ex) {
		Srtm.LOG.error(ex);
		return false;
	    }
	}
	int i = 0;
	while (i <= 1200) {
	    int j = 0;
	    while (j <= 1200) {
		try {
		    this.data[1200 - i][j] = (256 * s.read()) + s.read();
		} catch (final IOException ex) {
		    Srtm.LOG.error(ex);
		    return false;
		}
		j++;
	    }
	    i++;
	}
	try {
	    s.close();
	} catch (final IOException ex) {
	    Srtm.LOG.error(ex);
	    return false;
	}

	return true;
    }

    private boolean download(final String fname) {
	File output;
	final String region = Srtm.findRegion(fname, this.path, this.url);
	if (region == null) {
	    return false;
	}
	if (this.path.equals("")) {
	    output = new File(region + "/" + fname + ".zip");
	} else {
	    output = new File(this.path + "/" + region + "/" + fname + ".zip");
	}
	return Srtm.downloadFile(this.getUrl() + region + "/" + fname + ".zip", output);
    }

    /*
     * Returns region name for a file
     */
    private static String findRegion(final String fname, final String srtmPath, final String url) {
	if (Srtm.regionMap.isEmpty()) {
	    System.err.println("Downloading SRTM map data.");
	    String region;
	    for (int i = 0; i < Srtm.REGIONS.length; i++) {
		region = Srtm.REGIONS[i];
		String indexPath = region;
		if (!srtmPath.equals("")) {
		    indexPath = srtmPath + "/" + indexPath;
		}
		final File indexDir = new File(indexPath);
		if (!indexDir.exists()) {
		    indexDir.mkdirs();
		}
		indexPath += ".index.html";
		final File indexFile = new File(indexPath);
		if (!indexFile.exists()) {
		    if (!Srtm.downloadRegionIndex(i, srtmPath, url)) {
			// download error, try again with the next attempt
			Srtm.regionMap.clear();
			return null;
		    }
		}
		try {
		    final Scanner scanner = new Scanner(indexFile);
		    while (scanner.hasNext()) {
			final String line = scanner.next();
			if (line.contains("href=\"")) {
			    final int index = line.indexOf(".hgt.zip") - 7;
			    if (index >= 0) {
				final String srtm = line.substring(index, index + 7);
				Srtm.regionMap.put(srtm, i);
			    }
			}
		    }
		    scanner.close();
		} catch (final FileNotFoundException ex) {
		    Srtm.LOG.error(ex);
		}
	    }
	    Srtm.LOG.info("SRTM map filled in with " + Srtm.regionMap.size() + " entries.");
	}
	final String name = fname.replace(".hgt", "");
	if (Srtm.regionMap.containsKey(name)) {
	    return Srtm.REGIONS[Srtm.regionMap.get(name)];
	}
	return null;
    }

    private static boolean downloadRegionIndex(final int region, final String srtmPath, final String url) {
	String regionIndex = Srtm.REGIONS[region] + ".index.html";
	if (!srtmPath.equals("")) {
	    regionIndex = srtmPath + "/" + regionIndex;
	}
	final File regionIndexFile = new File(regionIndex);
	return Srtm.downloadFile(url + Srtm.REGIONS[region] + "/", regionIndexFile);
    }

    private static boolean downloadFile(final String urlAddress, final File output) {
	URL url1;
	InputStream inputs;
	BufferedOutputStream outputs;
	try {
	    url1 = new URL(urlAddress);
	} catch (final MalformedURLException ex) {
	    Srtm.LOG.error(ex);
	    return false;
	}
	try {
	    inputs = url1.openStream();
	    outputs = new BufferedOutputStream(new FileOutputStream(output));
	    int i = 0;
	    int ch = 0;
	    while (ch >= 0) {
		ch = inputs.read();
		if (ch >= 0) {
		    outputs.write(ch);
		}
		i++;
		if ((i % 1000) == 0) {
		    // progress.progress(i);
		}
	    }
	    inputs.close();
	    outputs.close();

	} catch (final FileNotFoundException ex) {
	    return false;
	} catch (final IOException ex) {
	    Srtm.LOG.error(ex);
	    return false;
	}

	return true;
    }

    public final void setPath(final String path) {
	this.path = path;
    }

    public String getUrl() {
	return this.url;
    }

    public final void setUrl(final String url) {
	this.url = url;
    }

    public static boolean exists(final int lon, final int lat) {
	final String fname = Srtm.getName(lon, lat);
	final String region = Srtm.findRegion(fname, Config.getInstance().getSrtm2OsmConfig().getCacheDir(), Config.getInstance().getSrtm2OsmConfig().getSrtmURL());
	return (region != null);
    }

    public static Srtm get(final int lon, final int lat) {
	final Srtm srtm = new Srtm();
	if (srtm.load(lon, lat)) {
	    return srtm;
	}
	return null;
    }

    /*
     * public double[] getCoords(int i, int j, CrsTransformation srtm2projected)
     * { if (easting[i][j] != 0d && northing[i][j] != 0d) { return new
     * double[]{easting[i][j], northing[i][j], data[i][j]}; } double di = i;
     * double dj = j; double ptLat = 1.0d * lat + project.getSrtmDlat() + di /
     * 1200d; double ptLon = 1.0d * lon + project.getSrtmDlon() + dj / 1200d;
     * double[] from = new double[3]; double[] to = new double[3]; from[0] =
     * ptLat; from[1] = ptLon; from[2] = data[i][j]; try { to =
     * srtm2projected.transform(from); } catch (Exception ex) {
     * Exceptions.printStackTrace(ex); } easting[i][j] = to[0]; northing[i][j] =
     * to[1]; return to; }
     */

    /*
     * Get SRTM elevation in meters for lon and lat WGS-84 coordinates
     */
    public static int getData(final double lon, final double lat) {
	final Srtm srtm = Srtm.get((int) Math.floor(lon), (int) Math.floor(lat));
	final int i = (int) Math.round(1200d * (lat - Math.floor(lat)));
	final int j = (int) Math.round(1200d * (lon - Math.floor(lon)));
	if (srtm == null) {
	    return -32768; // SRTM NaN
	}
	return srtm.data[i][j];
    }
}
